/**
 * @author:albert
 */

package com.bkbw.live.battle.business;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;

import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.badlogic.gdx.ai.btree.BehaviorTree;
import com.bkbw.live.battle.bean.PlayerInDungeon;
import com.bkbw.live.battle.bean.UserAgent;
import com.bkbw.live.battle.business.HateList.HateNode;
import com.bkbw.live.battle.business.ai.AiState;
import com.bkbw.live.battle.business.ai.BaseSelector;
import com.bkbw.live.battle.business.ai.BaseSequence;
import com.bkbw.live.battle.business.ai.BtAttack;
import com.bkbw.live.battle.business.ai.BtPatrolToCenter;
import com.bkbw.live.battle.business.ai.BtPursue;
import com.bkbw.live.battle.business.ai.BtRandomMove;
import com.bkbw.live.battle.business.ai.BtSearchEnemy;
import com.bkbw.live.battle.business.ai.BtSleep;
import com.bkbw.live.battle.business.items.UnitBag;
import com.bkbw.live.battle.business.items.UnitEquipment;
import com.bkbw.live.battle.business.items.UnitEquipmentBase;
import com.bkbw.live.battle.business.skill.AbilityBase;
import com.bkbw.live.battle.business.skill.AbilityBase.AbilityState;
import com.bkbw.live.battle.business.skill.ability.NullAbility;
import com.bkbw.live.battle.business.skill.AbilityFactory;
import com.bkbw.live.battle.database.bean.RoleBean;
import com.bkbw.live.battle.database.bean.WeaponsBean;
import com.bkbw.live.common.GameConfig.UnitMoveMode;
import com.bkbw.live.common.GameConfig;
import com.bkbw.live.common.fsm.GameStateMachine;
import com.bkbw.live.common.util.Utils;
import com.bkbw.live.common.util.Utils.FieldOperation;
import com.bkbw.live.common.dataproto.EnumConfigPb.AbilityType;
import com.bkbw.live.common.dataproto.EnumConfigPb.BulletCollisionMode;
import com.bkbw.live.common.dataproto.EnumConfigPb.BulletMoveMode;

public class UnitObject extends GameObject {
	
	Logger logger = LoggerFactory.getLogger(GameConfig.LoggerName);
	
	protected UnitBag bag = new UnitBag();
	public UnitBag getBag() {
		return bag;
	}
	
	
	// ---------------- variable & getter setter ----------------
	// 角色先天属性
	UnitNature baseNature = new UnitNature();
	
	public UnitNature getBaseNature() {
		return baseNature;
	}
	
	// 加上装备，buff，光环等之后的最终属性
	UnitNature finalNature = new UnitNature();

	public UnitNature getFinalNature() {
		return finalNature;
	}
	
	
	public void onPutOnEquip(UnitEquipmentBase equip){
		//Utils.operationSuperFieldsToBean(equip, finalNature, FieldOperation.FO_ADD);
		Utils.operationSuperFieldsFromBean(finalNature, equip, FieldOperation.FO_ADD);
	}
	
	public void onPutOffEquip(UnitEquipmentBase equip){
		//Utils.operationSuperFieldsToBean(equip, finalNature, FieldOperation.FO_SUB);
		Utils.operationSuperFieldsFromBean(finalNature, equip, FieldOperation.FO_SUB);
	}

	// ---------- attributes from mq_role -------
	protected int baseHp;
	protected int baseAttack;
	protected float baseVisionRange;
	protected int baseEvade;
	protected int baseArmor;
	protected int baseShield;
	
	protected int finalHp;
	protected int finalAttack;
	protected float finalVisionRange;
	protected float finalVisionRangeSqr;
	protected int finalEvade;
	protected int finalArmor;
	protected int finalShield;
	
	public int getFinalHp() {
		return finalHp;
	}
	
	public void setFinalHp(int hp) {
		if (hp > 0) {
			finalHp = hp;
			if (finalHp < currentHp) {
				currentHp = finalHp;
			}
		}
	}

	public void setFinalAttack(int attack) {
		this.finalAttack = attack;
	}
	public int getFinalAttack() {
		return finalAttack;
	}
	
	public float getFinalVisionRange() {
		return finalVisionRange;
	}

	public void setFinalVisionRange(float finalVisionRange) {
		this.finalVisionRange = finalVisionRange;
	}

	public int getFinalEvade() {
		return finalEvade;
	}

	public void setFinalEvade(int finalEvade) {
		this.finalEvade = finalEvade;
	}

	public int getFinalArmor() {
		return finalArmor;
	}

	public void setFinalArmor(int finalArmor) {
		this.finalArmor = finalArmor;
	}


	public int getFinalShield() {
		return finalShield;
	}

	public void setFinalShield(int finalShield) {
		this.finalShield = finalShield;
	}



	protected int currentHp;
	
	public int getCurrentHp() {
		return currentHp;
	}
	
	public void setCurrentHp(int value) {
		currentHp = value;
	}
	
	protected int roleId;

	public int getRoleId() {
		return roleId;
	}

	public void setRoleId(int roleId) {
		this.roleId = roleId;
	}
	
	// ----------- attribute from weapon ------------
	WeaponsBean weapon;
	
	public WeaponsBean getWeapon() {
		return weapon;
	}
	
	// ----------

	protected HateList hateList = new HateList();
	public HateList getHateList() {
		return hateList;
	}
	
	// 在追踪时根据仇恨改变敌人，条件是被追踪者在逃跑，其他单位对本单位造成的仇恨高于逃跑者，放弃当前目标切换为新的仇恨最高目标
	public boolean switchTargetEnemyByHateWhenPursue() {
		if (isTargetAttackable()) {
			HateNode maxHateNode = hateList.getMaxHateNode();
			HateNode currentNode = hateList.getHateNode(targetEnemy.getId());
			// 
			if (maxHateNode != null && currentNode != null && maxHateNode.getId() != currentNode.getId()) {
				if (currentNode.getHate() == 0) {
					UnitObject newTarget = map.getUnitObjectById(maxHateNode.getId());
					if (isTargetAttackable(newTarget)) {
						targetEnemy = newTarget;
						logger.debug("pursue switch hate");
						return true;
					}
				}
			}
		}
		logger.debug("pursue not switch hate");
		return false;
	}
	
	// 在攻击时根据仇恨改变敌人，条件是被攻击的目标是建筑，放弃建筑先打人
	public boolean switchTargetEnemyByHateWhenAttack() {
		
		if (isTargetAttackable()) {
			HateNode maxHateNode = hateList.getMaxHateNode();
			if (maxHateNode != null && targetEnemy instanceof BuildingObject) {
				UnitObject newTarget = map.getUnitObjectById(maxHateNode.getId());
				if (isTargetAttackable(newTarget)) {
					targetEnemy = newTarget;
					logger.debug("attack switch hate");
					return true;
				}
			}
		}
		logger.debug("attack not switch hate");
		return false;
	}
	
	protected UnitObject targetEnemy;
	
	public UnitObject getTargetEnemy() {
		return targetEnemy;
	}

	public void setTargetEnemy(UnitObject targetEnemy) {
		if (targetEnemy == null) {
			this.targetEnemy = null;
			return;
		}
		if (targetEnemy.getGroup() == this.group) {
			return;
		}
		this.targetEnemy = targetEnemy;
	}
	
	protected float thinkTick = 0.0f;
	protected float thinkTime = 0.2f;
	
	public enum ControlType {
		// 建筑，如果是防御性建筑会选择怪物进行攻击
		Building,
		// 玩家手动控制，移动和攻击
		Manual,
		// 玩家半自动控制，只负责移动，ai控制攻击
		SemiAuto,
		// 玩家掉线了，ai托管，只被动还击，不主动攻击，不主动探索移动
		Managed, 
		// 自动移动探索，开宝箱，攻击
		Auto,
		// 普通怪物
		Monster,
		// 精英和boss的ai
		Boss,
		// 类似auto，模拟玩家行为的机器人，让用户获得更好体验
		Hero,
		
	}
	
	protected ControlType controlType;
	protected boolean isRobot;
	
	protected BehaviorTree<UnitObject> brain;
	
	protected AiState state;
	
	public AiState getState() {
		return state;
	}
	
	protected boolean isAlive;
	

	public boolean isAlive() {
		return isAlive;
	}

	protected AbilityBase normalAttack;
	
	protected UnitMoveMode moveMode;
	
	public UnitMoveMode getMoveMode() {
		return moveMode;
	}

	public void setMoveMode(UnitMoveMode moveMode) {
		this.moveMode = moveMode;
	}
	
	protected Vector3D moveTarget;

	public Vector3D getMoveTarget() {
		return moveTarget;
	}

	public void setMoveTarget(Vector3D moveTarget) {
		this.moveTarget = moveTarget;
	}
	
	public boolean isNearTarget(Vector3D pos) {
		if (pos.subtract(position).getNormSq() <= 1.0D) {
			return true;
		}
		return false;
	}
	
	public boolean isAtTarget(Vector3D pos) {
		if (pos.subtract(position).getNormSq() <= 0.01D) {
			return true;
		}
		return false;
	}
	
	protected boolean isAttacking = false;

	public boolean isAttacking() {
		return isAttacking;
	}

	public void setAttacking(boolean isAttacking) {
		this.isAttacking = isAttacking;
	}
	
	protected boolean userMove = false;
	
	public boolean isUserMove() {
		return userMove;
	}

	public void setUserMove(boolean userMove) {
		if (userMove) {
			if (!this.userMove) {
				if (brain != null) {
					brain.cancel();
				}
			}
		}
		this.userMove = userMove;
	}
	
	public void setMoving(boolean move) {
		//Thread.currentThread().dumpStack();
		//if (!isUserControl())
		//	Thread.dumpStack();
		if (move != isMoving) {
			if (move) {
				//this.targetEnemy = null;
				this.getMap().unitMoveStateChange(id, 1);
			}
			else {
				this.getMap().unitMoveStateChange(id, 0);
			}
			isMoving = move;
		}
	}
	
	

	// ----------------- constructor ------------------
	public UnitObject(int id, GameLogicMap map) {
		super(id, map);
		state = AiState.Idle;
		this.currentHp = 100;
		this.finalHp = 100;
		this.finalAttack = 2;
		isAlive = true;
		bag.setUnitObject(this);
	}
	
	public void init(ControlType controlType, RoleBean role, WeaponsBean weapon){
		init(controlType, role);
		this.weapon = weapon;
		this.finalAttack += this.weapon.getAttack();
		
		//bindAbility(NullAbility.getInstance().getAbilityId(), NullAbility.getInstance());
	}
	
	public void init(ControlType controlType, RoleBean role){
		init(controlType);
		baseHp = role.getHp();
		baseAttack = role.getAttack();
		baseArmor = role.getArmor();
		baseEvade = role.getEvade();
		baseVisionRange = role.getVisionRange();
		baseShield = role.getShield();
		finalHp = baseHp;
		finalAttack = baseAttack;
		finalArmor = baseArmor;
		finalEvade = baseEvade;
		finalShield = baseShield;
		finalVisionRange = baseVisionRange;
		finalVisionRangeSqr = finalVisionRange * finalVisionRange;
		logger.debug("id "+id+" sight is "+finalVisionRange);
		currentHp = finalHp;
		this.baseNature.setSight(finalVisionRange);
		setSpeedAndHead(new Vector3D(role.getSpeed(), 0, 0));
	}
	
	public float getSight() {
		if (this.baseNature != null) {
			return baseNature.getSight();
		}
		return 0f;
	}
	
	public float getAttackRange() {
		if (this.weapon != null) {
			return this.weapon.getAttackRange();
		}
		return 0f;
	}
	
	public void init(ControlType controlType){
		setControlType(controlType);
	}
	
	public void setControlType(ControlType controlType)
	//public void init(ControlType controlType)//, HashMap<String, Object> param)
	{
		
		this.controlType = controlType;
		switch (this.controlType) {
		case Building:
		{
			buildBuildBrain();
			break;
		}
		case Manual:
		{
			buildManualBrain();
			break;
		}
		case SemiAuto:
		{
			break;
		}
		case Managed:
		{
			break;
		}
		case Auto:
		{
			break;
		}
		case Monster:
		{
			buildMonsterBrain();
			setSpeedAndHead(new Vector3D(5, 0, 0));
			break;
		}
		case Boss:
		{
			break;
		}
		case Hero:
		{
			break;
		}
		default:
		{
			buildMonsterBrain();
			break;
		}
		}
	}
	
	protected void buildBuildBrain() {
		BaseSelector<UnitObject> rootSelector = new BaseSelector<UnitObject>();
		BtAttack<UnitObject> attack = new BtAttack<UnitObject>();
		BtSearchEnemy<UnitObject> search = new BtSearchEnemy<UnitObject>();
		rootSelector.addChild(attack);
		rootSelector.addChild(search);
		brain = new BehaviorTree<UnitObject>(rootSelector, this);
	}
	
	protected void buildManualBrain()
	{
		BaseSelector<UnitObject> rootSelector = new BaseSelector<UnitObject>();
		BtAttack<UnitObject> attack = new BtAttack<UnitObject>();
		//BtPursue<UnitObject> pursue = new BtPursue<UnitObject>();
		rootSelector.addChild(attack);
		//rootSelector.addChild(pursue);
		brain = new BehaviorTree<UnitObject>(rootSelector, this);
	}
	
	protected void buildMonsterBrain()
	{
		BaseSelector<UnitObject> rootSelector = new BaseSelector<UnitObject>();
		BaseSelector<UnitObject> attackSelector = new BaseSelector<UnitObject>();
		BtAttack<UnitObject> attack = new BtAttack<UnitObject>();
		BtPursue<UnitObject> pursue = new BtPursue<UnitObject>();
		BtSearchEnemy<UnitObject> search = new BtSearchEnemy<UnitObject>();
		attackSelector.addChild(attack);
		attackSelector.addChild(pursue);
		attackSelector.addChild(search);
		//BtRandomMove<UnitObject> randomMove = new BtRandomMove<UnitObject>();
		BtPatrolToCenter<UnitObject> patrolToCenter = new BtPatrolToCenter<UnitObject>();
		rootSelector.addChild(attackSelector);
		//rootSelector.addChild(randomMove);
		rootSelector.addChild(patrolToCenter);
		brain = new BehaviorTree<UnitObject>(rootSelector, this);
		
		/*
		 BaseSequence<UnitObject> root = new BaseSequence<UnitObject>();
		BtSleep<UnitObject> btSleep = new BtSleep<UnitObject>();
		root.addChild(btSleep);
		root.addChild(rootSelector);
		brain = new BehaviorTree<UnitObject>(root, this);
		 */
		
		
//		BaseSequence<UnitObject> root = new BaseSequence<UnitObject>();
//		BtRandomMove<UnitObject> randomMove = new BtRandomMove<UnitObject>();
//		BtSleep<UnitObject> btSleep = new BtSleep<UnitObject>();
//		root.addChild(btSleep);
//		root.addChild(randomMove);
//		brain = new BehaviorTree<UnitObject>(root, this);
	}
	
	UserAgent agent;
	
	public UserAgent getAgent() {
		return agent;
	}

	public void setAgent(UserAgent agent) {
		this.agent = agent;
	}

	public void recvUserMove(Vector3D pos)
	{
		position = pos;
	}

	public void startMove()
	{
		//System.out.println("id " + id + " start move "+position);
		setMoving(true);
		setUserMove(true);
		AiState oldState = this.state;
		this.state = AiState.Move;
		if (oldState == AiState.Attack) {
			this.normalAttack.cancel();
		}
		setTargetEnemy(null);
		/*currentUnit.setMoving(move==1?true:false);
		if (move == 1) {
			currentUnit.setTargetEnemy(null);
		}*/
	}
	
	public void stopMove()
	{
		//System.out.println("id " + id + " stop move "+position);
		setMoving(false);
		setUserMove(false);
		this.state = AiState.Idle;
	}

	
	public void think(float dt) {
	
		if (controlType == ControlType.Building) {
			if (brain != null) {
				brain.step();
			}
		}
		else if (controlType == ControlType.Manual) {
			if (brain != null && !isMoving) {
				brain.step();
			}
		}
		else if (controlType == ControlType.SemiAuto) {
			thinkTick += dt;
			if (thinkTick < thinkTime) {
				return;
			}
			else {
				thinkTick = 0;
			}
			//System.out.println("think()");
			if (this.state == AiState.None) {
				return;
			}
			if (this.state == AiState.Idle) {
				
				searchEnemy();
				if (targetEnemy != null) {
					if (isTargetInDistance()) {
						this.state = AiState.Attack;
						System.out.println("find enemy start attack");
					}
					else {
						//System.out.println("enemy too far");
						//this.state = AiState.MoveAndAttack;
					}
				}
			}
			else if (this.state == AiState.Attack) {
				//System.out.println("attack state");
				if (isTargetAttackable()) {
					if (!isTargetInDistance()) {
						//this.state = AiState.MoveAndAttack;
						//System.out.println("enemy too far");
						this.state = AiState.Idle;
					} else {
						//System.out.println("apply");
						this.normalAttack.apply();
					}
				}
				else {
					System.out.println("target not attackable");
					searchEnemy();
					if (targetEnemy != null) {
						this.state = AiState.Attack;
					}
					else {
						this.state = AiState.Idle;
					}
				}
			}
			else if (this.state == AiState.MoveAndAttack) {
				System.out.println("move and attack state");
				if (isTargetAttackable()) {
					if (isTargetInDistance()) {
						this.state = AiState.Attack;
					}
				}
				else {
					searchEnemy();
					if (targetEnemy != null) {
						this.state = AiState.Attack;
					}
					else {
						this.state = AiState.Idle;
					}
				}
			}
			else {
				//System.out.println("impossible state " + this.state);
			}
		}
		else if (controlType == ControlType.Monster) {
			brain.step();
		}
	}
	
	@Override
	public void update(float dt) 
	{
		if (!isAlive) {
			return;
		}
		this.hateList.updateHateByTime(dt);
		this.think(dt);
		for (AbilityBase ability : abilitys.values()) {
			ability.update(dt);
		}
		super.update(dt);
		this.onMove(dt);
	}
	
	@Override
	public void unbindAll()
	{
		for (AbilityBase ability : abilitys.values()) {
			unbindAbility(ability.getAbilityId());
		}
		super.unbindAll();
	}
	
	public void onMove(float dt)
	{
		if (!isUserControl()) {
			if (isMoving()) {
				if (moveMode == UnitMoveMode.Target) {
					Vector3D pos = this.moveTarget;
					Vector3D vc = pos.subtract(position);
//					if (isAtTarget(pos)) {
//						setPosition(moveTarget);
//					}
//					
					if (vc.getNorm() <= 0.01D) {
						setPosition(moveTarget);
					}
					else {
						
						//vc.normalize().scalarMultiply(speedLen).scalarMultiply(dt)
						Vector3D movement = vc.normalize().scalarMultiply(speedLen * dt);
						if (vc.getNormSq() > movement.getNormSq()) {
							Vector3D newpos = getPosition().add(movement);
							setPosition(newpos);
						}
						else {
							setPosition(moveTarget);
						}
						
					}
					//logger.debug("target " + id + " move to " + position);;
				}
				else {
					Vector3D speed = getSpeed();
					Vector3D movement = speed.scalarMultiply(dt);
					Vector3D pos = getPosition().add(movement);
					setPosition(pos);
				}
				
			}
		}
	}
	
	public void onAttack(float dt)
	{
		if (this.state == AiState.Attack) {
			this.normalAttack.apply();
		}
	}
	
	public void doNormalAttack()
	{
		this.normalAttack.apply();
	}
	
	public AbilityBase getNormalAttack() 
	{
		return this.normalAttack;
	}
	
	public boolean isAttack() 
	{
		return this.normalAttack.isSpelling();
//		if (this.normalAttack.getState() != AbilityState.Available) {
//			return true;
//		}
//		return false;
	}
	
	public void onHpChange(GameObject obj, int point)
	{
		if (point < 0 && obj != null && !(obj instanceof BuildingObject)) {
			hateList.subHate(obj.getId(), HateList.NormalAttackHate);
		}
		this.currentHp += point;
		if (this.currentHp < 0) {
			this.currentHp = 0;
		}
		else if (this.currentHp > this.finalHp) {
			this.currentHp = this.finalHp;
		}
		//System.out.println("id " + getId() + " hp is " + currentHp);
		//logger.debug("id " + getId() + " hp is " + currentHp + " attack by " + obj.getId());
		if (this.currentHp <= 0) {
			onDead();
		}
	}
	
	public void onDead()
	{
		isAlive = false;
		if (brain != null) {
			brain.cancel();
		}
		//this.state = AiState.None;
	}
	
	public boolean canAttack(UnitObject enemy) {
		if (enemy != null && enemy.attackable() && enemy.getGroup() != getGroup()) {
			return true;
		}
		return false;
	}
	
	
	public UnitObject searchEnemy()
	{
		
		//System.out.println("searchEnemy()");
		if (targetEnemy != null && targetEnemy.attackable()) {
			return null;
		}
		LinkedList<DistanceObject<UnitObject>> list = this.map.getEnemysInDistance(this, finalVisionRange);
		if (list.size() >= 1) {
			list.sort((DistanceObject<UnitObject> o1, DistanceObject<UnitObject> o2) -> {
				if (o1.distanceSqr > o2.distanceSqr)
					return 1;
				else if (o1.distanceSqr == o2.distanceSqr)
					return 0;
				else 
					return -1;
			});
			targetEnemy = list.get(0).gameObject;
			//System.out.println("searchEnemy find");
			return targetEnemy;
		}
		return null;
	}
	
	public boolean isTargetInDistance()
	{
		float distanceSqr = (float)this.getPosition().distanceSq(targetEnemy.getPosition());
		if (normalAttack.getDistanceSqr() >= distanceSqr) {
			return true;
		}
		return false;
	}
	
	public boolean isTargetInPursueDistance() 
	{
		float distanceSqr = (float)this.getPosition().distanceSq(targetEnemy.getPosition());
		if (normalAttack.getDistanceSqr() >= distanceSqr + 0.1) {
			return true;
		}
		return false;
	}
	
	public boolean isTargetInSight() {
		float distanceSqr = (float)this.getPosition().distanceSq(targetEnemy.getPosition());
		if (finalVisionRangeSqr >= distanceSqr) {
			return true;
		}
		return false;
	}
	
	public boolean isTargetAttackable() 
	{
		if (targetEnemy != null && targetEnemy.attackable()) {
			return true;
		}
		return false;
	}
	
	public boolean isTargetAttackable(UnitObject unit) 
	{
		if (unit != null && unit.attackable()) {
			return true;
		}
		return false;
	}
	
	@Override
	public boolean attackable()
	{
//		if (this.state == AiState.None)
//			return false;
//		return true;
		return isAlive;
	}
	
	// ------- ability -----
	protected HashMap<String, AbilityBase> abilitys = new HashMap<String, AbilityBase>();
	public boolean hasAbility(String abilityId) 
	{
		return this.abilitys.containsKey(abilityId);
	}
	
	public void bindAbility(String abilityClass, String abilityId, HashMap<String, Object> param)
	{
		if (hasAbility(abilityId)) {
			return;
		}
		
		AbilityFactory factory = AbilityFactory.getInstance();
		AbilityBase ability = (AbilityBase)factory.createAbility(abilityClass, abilityId, param);
		System.out.println("??"+ability);
		if (this.normalAttack == null && ability.getAbilityType() == AbilityType.NormalAttack) {
			this.normalAttack = ability;
			logger.debug("id "+id+" attack distance "+normalAttack.getDistance());
		}
		ability.bind(this);
		abilitys.put(abilityId, ability);
	}
	
	public void bindAbility(String abilityId, AbilityBase ability) 
	{
		if (this.normalAttack == null && ability.getAbilityType() == AbilityType.NormalAttack) {
			this.normalAttack = ability;
		}
		ability.bind(this);
		abilitys.put(abilityId, ability);
	}
	
	public void unbindAbility(String abilityId)
	{
		if (hasAbility(abilityId)) {
			this.abilitys.remove(abilityId);
		}
	}
	
	

	// -------------- user control ----------------
	public boolean isMonster() 
	{
		return (controlType == ControlType.Monster) || (controlType == ControlType.Boss);
	}
	
	public boolean isUserControl()
	{
		if (controlType == ControlType.Manual) {
			return true;
		}
		if (controlType == ControlType.SemiAuto) {
			return true;
		}
		return false;
	}

}
